// @ts-ignore
import {posix} from 'path';
import {ROUTE_PREFIX, MW_PREFIX, PARAMS_PREFIX, HTTP_METHODS} from '../constants';

export const Controller = (path: string = '') => function (target) {
    const proto = target.prototype;
    // get middlewares
    const mws = Reflect.getMetadata(MW_PREFIX, target) || [];

    //get routes
    const routeDefs = Reflect.getMetadata(ROUTE_PREFIX, proto) || [];
    const routes = [];

    for (const route of routeDefs) {
        const fnMws = Reflect.getMetadata(`${MW_PREFIX}_${route.name}`, proto) || [];
        const params = Reflect.getMetadata(`${PARAMS_PREFIX}_${route.name}`, proto) || [];

        routes.push({
            method: route.method,
            url: posix.join('/', path, route.path),
            middleware: [...mws, ...fnMws],
            name: route.name,
            params
        });
    }

    Reflect.defineMetadata(ROUTE_PREFIX, routes, target);
};

/**
 * Middleware(s) decorator
 * */
export const Use = (...middlewares: any[]) => {
    return (target, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
        if (!propertyKey) {
            propertyKey = '';
        } else {
            propertyKey = '_' + propertyKey;
        }
        Reflect.defineMetadata(`${MW_PREFIX}${propertyKey}`, middlewares, target);
    }
};

/**
 * Route method decorator
 * */
export const Route = (method: string, path: string = '') => {
    return (target, name, descriptor: TypedPropertyDescriptor<any>) => {
        const meta = Reflect.getMetadata(ROUTE_PREFIX, target) || [];
        meta.push({method, path, name});
        Reflect.defineMetadata(ROUTE_PREFIX, meta, target);
    }
};

export const Get = (path?: string) => Route(HTTP_METHODS.GET, path);

export const Post = (path?: string) => Route(HTTP_METHODS.POST, path);

export const Put = (path?: string) => Route(HTTP_METHODS.PUT, path);

export const Patch = (path?: string) => Route(HTTP_METHODS.PATCH, path);

export const Delete = (path?: string) => Route(HTTP_METHODS.DELETE, path);

export const Inject = fn => {
    return (target, name, index) => {
        const meta = Reflect.getMetadata(`${PARAMS_PREFIX}_${name}`, target) || [];
        meta.push({index, name, fn});
        Reflect.defineMetadata(`${PARAMS_PREFIX}_${name}`, meta, target);
    }
};

export const Ctx = () => Inject(ctx => ctx);

export const Req = () => Inject(ctx => ctx.req);

export const Request = () => Inject((ctx) => ctx.request);

export const Res = () => Inject((ctx) => ctx.res);

export const Response = () => Inject((ctx) => ctx.response);

export const Body = () => Inject((ctx) => ctx.request.body);

export const Fields = () => Inject((ctx) => ctx.request.fields);

export const File = () => Inject((ctx) => {
    if (ctx.request.files.length) return ctx.request.files[0];
    return ctx.request.files;
});

export const FileUpload = () => Inject(ctx => ctx.req.file);

export const Files = () => Inject((ctx) => ctx.request.files);

export const QueryParam = (prop?) => Inject((ctx) => {
    if (!prop) return ctx.query;
    return ctx.query[prop];
});

export const QueryParams = () => QueryParam();

export const Param = (prop?) => Inject((ctx) => {
    if (!prop) return ctx.params;
    return ctx.params[prop];
});

export const Params = () => Param();
